package hxm.core;

import haxe.macro.Expr;
import haxe.macro.Context;

import tink.macro.tools.MacroTools;

import hxm.core.MExpr;
import hxm.monads.OptionMonad;

#if macro
using hxm.core.MacroHelper;
using tink.macro.tools.ExprTools;
using tink.core.types.Outcome;
#end

/**
 * The generator generates an Expr from a TMExpr.
 * 
 * @author Simon Krajewski
 */
class Generator 
{
	#if macro
	
	static public function generateCalls(mexpr:TMExpr):TMExpr
	{
		switch(mexpr.expr)
		{
			default:
				return mexpr;
			case MExpr(e):
				switch(e.expr)
				{
					case ECall(c, p):
						return mexpr;
					default:
						return mexpr;
				}
		}
	}
	
	static public function generateExpr(mexpr:TMExpr):Expr
	{
		switch(mexpr.expr)
		{
			case MCall(e, p, i):
				// Here we bind the called function to null and add the current monad class as first parameter.
				switch(mexpr.type)
				{
					case None:
						return Context.error("[Generator] Failed to determine monadic type.", mexpr.pos);
					case Some(typeInfo):
						switch(Resolver.resolveMonadicClass(typeInfo.name))
						{
							case None:
								return Context.error("[Generator] Failed to determine monadic type.", mexpr.pos);
							case Some(cls):
								var v = [];
								for (param in p) v.push(generateExpr(param));
								v.unshift(Context.parse(cls.path, Context.currentPos()));
								var func = EFunction(null,
									Exprs.func(generateExpr(i), [Exprs.toArg("null")])
								).at();
								return monadicCall(typeInfo, "bind", [ECall(generateExpr(e), v).at(e.pos), func]);
						}

				}
			case MType(t):
				// This was just relevant to the typer, so we return an empty block.
				return EBlock([]).at(mexpr.pos);
			case MSub(b):
				// We generate the sub-expression and require a monadic context that we can yield the sub-context into.
				var sub = generateExpr(b);
				switch(mexpr.type)
				{
					case None:
						return Context.error("[Generator] Failed to determine monadic type.", mexpr.pos);
					case Some(typeInfo):
						return monadicCall(typeInfo, "yield", [sub]);
				}
			case MReturn(v):
				// Basically the same as MSub. 
				switch(mexpr.type)
				{
					case None:
						return Context.error("[Generator] Failed to determine monadic type.", mexpr.pos);
					case Some(typeInfo):
						var ret = if (v == null)
						{
							Context.warning("[Generator] I don't know what to do with empty return, returning null instead.", mexpr.pos);
							EConst(CIdent("None")).at();
						}
						else
							generateExpr(v);
						return monadicCall(typeInfo, "yield", [ret]);
				}

			case MExpr(e):
				return e;
			case MBlock(b):
				// Straight conversion to EBlock.
				var exprs = [];
				for (e in b)
					exprs.push(generateExpr(e));
				return EBlock(exprs).at();
			case MBind(ident, bind, inner):
				// We generate the sub-expressions and create the appropriate function and the bind call.
				switch(mexpr.type)
				{
					case None:
						return Context.error("[Generator] Failed to determine monadic type.", mexpr.pos);
					case Some(typeInfo):
						var identExpr = generateExpr(ident);

						var bindExpr = generateExpr(bind);
						var identName = Exprs.getIdent(identExpr).data();
						var innerExpr = generateExpr(inner);
						var func = EFunction(null,
							Exprs.func(innerExpr.expr.at(inner.pos), [Exprs.toArg(identName)])
						).at();
						return monadicCall(typeInfo, "bind", [bindExpr, func]);
				}
		}
	}

	static public function monadicCall(typeInfo:TypeInfo, func, args):Expr
	{
		switch(Resolver.resolveMonadicClass(typeInfo.name))
		{
			case None:
				throw "Internal error: The typer should have checked this, but it came through to the generator.";
			case Some(classInfo):
				return ECall(Context.parse(classInfo.path + "." +func, Context.currentPos()), args).at();
		}
		
	}
	
	#end
}